package filecopy;

/*
 * FileCopyClient.java Version 0.1 - Muss erg�nzt werden!! Praktikum 3
 * Rechnernetze BAI4-SS2012 HAW Hamburg Autoren:
 */
import java.io.*;
import java.net.*;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;

public class FileCopyClient extends Thread {

    // -------- Constants
    public final static boolean TEST_OUTPUT_MODE = false;
    public final int SERVER_PORT = 23000;
    public final int UDP_PACKET_SIZE = 1024 - 8;
    // -------- Public parms
    public String servername;
    public String sourcePath;
    public String destPath;
    public int windowSize;
    public long serverErrorRate;
    // -------- Variables
    // current default timeout in nanoseconds
    private long timeoutValue = 10000000;
    private DatagramSocket connection;
    private FileInputStream file;
    private final Window window;
    private int sendBase = 0;
    private int nextSequenceNumber = 0;
    private long remainingFileSize;
    private DatagramPacket udpPacket;
    private long estimatedRTT = -1;
    private long deviation = 0;
    public int lostPackages = 0;
    
    private long startTime;
    private int timeoutCount = 0;

    // Constructor
    public FileCopyClient(String serverArg, String sourcePathArg,
            String destPathArg, String windowSizeArg, String errorRateArg) throws IOException {
        servername = serverArg;
        sourcePath = sourcePathArg;
        destPath = destPathArg;
        windowSize = Integer.parseInt(windowSizeArg);
        serverErrorRate = Long.parseLong(errorRateArg);

        connection = new DatagramSocket();
        file = new FileInputStream(sourcePath);
        remainingFileSize = new File(sourcePath).length();
        window = new Window(windowSize, this);
    }

    public void runFileCopyClient() throws IOException, InterruptedException {
        startTime = System.currentTimeMillis();
        FCpacket packet = this.makeControlPacket();
        udpPacket = new DatagramPacket(packet.getSeqNumBytesAndData(), packet.getLen() + 8, InetAddress.getByName(servername), SERVER_PORT);
        connection.send(udpPacket);
        nextSequenceNumber = 1;
        //Thread zum Warten auf Quittungen erzeugen

        AckThread ackThread = new AckThread(connection, UDP_PACKET_SIZE, window, this);
        ackThread.start();
        //Nun Datei senden
        //Window befüllen
        while (remainingFileSize > 0) {
            byte[] buffer = new byte[UDP_PACKET_SIZE];
            int length = file.read(buffer,0, UDP_PACKET_SIZE);
            if (length > 0) {
                FCpacket pack = new FCpacket(nextSequenceNumber++, buffer, length);
                window.add(pack);
                System.out.println("\t\t\tSending packet " + (nextSequenceNumber -1));
                sendPacket(pack);
                remainingFileSize -= length;
            } else 
                remainingFileSize = 0;
        }
        System.out.println("Wait window:" + window);
        window.waitForEmptyWindow();
        ackThread.interrupt();
        //Dateitransfer abgeschlossen
        connection.close();
        System.out.println("Transfer dauerte: " + (System.currentTimeMillis() - startTime) + "ms");
        System.out.println("Timeouts: " + timeoutCount);
    }

    private void sendPacket(FCpacket p) throws IOException {
        //System.out.println("Sent packet " + p.getSeqNum());
        udpPacket.setData(p.getSeqNumBytesAndData());
        p.setSent(true);
        p.setTimestamp(System.nanoTime());
        this.startTimer(p);
        connection.send(udpPacket);

    }

    /**
     *
     * Timer Operations
     */
    public void startTimer(FCpacket packet) {
        /*
         * Create, save and start timer for the given FCpacket
         */
        FC_Timer timer = new FC_Timer(timeoutValue, this, packet.getSeqNum());
        packet.setTimer(timer);
        timer.start();
        //System.out.println("\t\t\t\t\tTimer für " + packet.getSeqNum() + " gestartet");
    }

    public void cancelTimer(FCpacket packet) {
        /*
         * Cancel timer for the given FCpacket
         */
        //System.out.println("Cancel Timer for packet" + packet.getSeqNum());

        if ((packet.getTimer() != null)) {
            packet.getTimer().interrupt();
        }
    }

    /**
     * Implementation specific task performed at timeout
     */
    public void timeoutTask(long seqNum) throws IOException {
       // System.out.println("Timeout for " + seqNum);
        FCpacket p = window.getBySequenceNumber(seqNum);
        if (p != null && !p.isValidACK()) //Wenn null, dann hat AckThread das Paket freigegeben
            sendPacket(p);
        ++timeoutCount;
    }

    /**
     *
     * Computes the current timeout value (in nanoseconds)
     */
    public void computeTimeoutValue(long sampleRTT) {
        double x = 0.1;
        //System.out.println("sampleRTT:" + sampleRTT);
        if (estimatedRTT == -1)
            estimatedRTT = sampleRTT;
        estimatedRTT = (long)((1-x)*estimatedRTT + x*sampleRTT);
        deviation = (long) ((1-x)*deviation + x* Math.abs(sampleRTT-estimatedRTT));
        timeoutValue = estimatedRTT + 4*deviation;
        //System.out.println("timeout: "+ timeoutValue + " deviation: " + deviation + " estimatedRTT:" + estimatedRTT);
    }

    /**
     *
     * Return value: FCPacket with (0 destPath;windowSize;errorRate)
     */
    public FCpacket makeControlPacket() {
        /*
         * Create first packet with seq num 0. Return value: FCPacket with (0
         * destPath ; windowSize ; errorRate)
         */
        String sendString = destPath + ";" + windowSize + ";" + serverErrorRate;
        byte[] sendData = null;
        try {
            sendData = sendString.getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return new FCpacket(0, sendData, sendData.length);
    }

    public void testOut(String out) {
        if (TEST_OUTPUT_MODE) {
            System.out.println("Client: " + out);
        }
    }

    public static void main(String argv[]) throws Exception {
        FileCopyClient myClient = new FileCopyClient(argv[0], argv[1], argv[2],
                argv[3], argv[4]);
        myClient.runFileCopyClient();
    }
}
